﻿using System;
using System.Collections.Generic;
using Bling.DSL;
using Bling.Util;
using Bling.Linq;
using Bling.Core;
using Bling.Matrices;

namespace Bling.Core {
  using Bling.Matrices;
  using Bling.Derivatives;
  public static partial class BrandExtensions {
    //IDimPointBl<PORTABLE, DIM>
    public static Func<DoubleBl, POINT> Derivative<POINT, DIM>(this Func<DoubleBl, POINT> F)
      where POINT : FixDoubleBl<DIM,POINT>
      where DIM : Dim1D<DIM>, IMultiDim1D<DIM> {
      var Target = new TargetExpr<double>("T", 1d.Bl());
      var P = F(Target);
      var arity = Dim<DIM>.Count;
      var Ds = new Expr<double>[arity];
      for (int i = 0; i < arity; i++) {
        Ds[i] = P[i].Provider.DerivativeX;
        if (Ds[i] == null) return null; // no derivative.
        Ds[i] = Ds[i].Fold;
      }
      return T => {
        var replace = new ReplaceEval();
        replace.Replace[Target] = T.Provider;
        var Fs = new DoubleBl[arity];
        for (int i = 0; i < Fs.Length; i++) Fs[i] = replace.Translate(Ds[i]);
        return FixDoubleBl<DIM,POINT>.Make(Fs);
      };
    }
    public static Func<Double2Bl, MatrixBl<D3, D2>> Derivative<BRAND>(this ArgDoubleFuncBl<Double2Bl, Double3Bl, BRAND> F) where BRAND : ArgDoubleFuncBl<Double2Bl, Double3Bl, BRAND> {
      return F.Function.Derivative();
    }
    public static Func<Double2Bl,MatrixBl<D3, D2>> Derivative(this Func<Double2Bl, Double3Bl> F) {
      // R_2 => R_3
      // result = 2 by 3 matrix
      // produce n by m matrix

      AsConstant<double>[] AsConst = new AsConstant<double>[] {
        new AsConstant<double>("AX"), new AsConstant<double>("AY"),
      };
      TargetExpr<double>[] Targets = new TargetExpr<double>[] {
        new TargetExpr<double>("X", 1d.Bl()), new TargetExpr<double>("Y", 1d.Bl()),
      };
      DoubleBl[,] M = new DoubleBl[2, 3];
      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 2; j++) {
          var XY = new Double2Bl(j == 0 ? (Expr<double>) Targets[0] : AsConst[0],
                               j == 1 ? (Expr<double>) Targets[1] : AsConst[1]);
          var target = F(XY)[i].Provider.Fold;
          var mji = target.DerivativeX;
          if (mji == null) 
            return null;
          M[j, i] = mji;
        }
      }
      return P => {
        // replace....
        var replace = new ReplaceEval();
        replace.Replace[AsConst[0]] = P.X.Provider;
        replace.Replace[Targets[0]] = P.X.Provider;
        replace.Replace[AsConst[1]] = P.Y.Provider;
        replace.Replace[Targets[1]] = P.Y.Provider;
        DoubleBl[,] M1 = new DoubleBl[2, 3];
        for (int i = 0; i < 3; i++) {
          for (int j = 0; j < 2; j++) {
            M1[j,i] = replace.Translate(M[j,i].Provider);

          }
        }
        return new MatrixBl<D2, D3>(M1).Transpose;
      };
    }

  }
  public abstract partial class Brand<T, BRAND> : Brand<BRAND>, IBrandT<T>, IExtends<BRAND,ObjectBl>, IBrand<T, BRAND> where BRAND : Brand<T, BRAND> {
    public BRAND Extent(bool Max) {
      return ToBrand(Provider.Extent(Max));
    }
  }




}
namespace Bling.Derivatives {
  public class AsConstant<T> : ParameterExpr<T> {
    public AsConstant(object key) : base(key) { }
    protected override Expr<T> Derivative1(Dictionary<Expr,Expr> Map) {
      return new Constant<T>(default(T)); 
    }
  }
  public class TargetExpr<T> : ParameterExpr<T> {
    private readonly Expr<T> One;
    public TargetExpr(object key, Expr<T> One) : base(key) { this.One = One;  }
    protected override Expr<T> Derivative1(Dictionary<Expr, Expr> Map) {
      return One;
    }
  }

}

namespace Bling.DSL {
  using Bling.Ops;
  public abstract partial class Expr<T> : Expr, IExpr<T> {
    public Expr<T> DerivativeX {
      get {
        return Derivative1(new Dictionary<Expr,Expr>());
      }
    }
    public Expr<T> Derivative(Dictionary<Expr, Expr> Map) { return Derivative1(Map); }
    protected virtual Expr<T> Derivative1(Dictionary<Expr, Expr> Map) {
      return null;
    }
    public virtual Expr<T> Extent(bool Max) {
        throw new NotSupportedException();
    }

  }
  public partial class Constant<T> : Expr<T>, IConstant {
    protected override Expr<T> Derivative1(Dictionary<Expr,Expr> Map) {
      if (typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(Vecs.IVec<,>)) {
        return new Constant<T>((T)(object)Vecs.VecUtils.DefaultFor(typeof(T)));
      }


      return new Constant<T>(default(T)); 
    }
    public override Expr<T> Extent(bool Max) {
      return this;
    }
  }
  public abstract partial class ProxyExpr<T> : Expr<T>, IProxyExpr {
    protected override Expr<T> Derivative1(Dictionary<Expr,Expr> Map) {
      return Make(Underlying.Derivative(Map)); // remain proxied 
    }
    public override Expr<T> Extent(bool Max) {
      return Underlying.Extent(Max);
    }
  }
}

namespace Bling.Ops {
  public partial interface IOperatorX<S, T> : IOperator<T> {
    Expr<T> Derivative(Expr<S> argA, Dictionary<Expr, Expr> Map);
    Expr<T> Extent(Expr<S> argA, bool Max);
  }
  public partial interface OperatorX<S, T, U> : IOperator<U> {
    Expr<U> Derivative(Expr<S> argA, Expr<T> argB, Dictionary<Expr, Expr> Map);
    Expr<U> Extent(Expr<S> argA, Expr<T> argB, bool Max);
  }
  public partial interface OperatorX<S, T, U, V> : IOperator<V> {
    Expr<V> Derivative(Expr<S> argA, Expr<T> argB, Expr<U> argC, Dictionary<Expr, Expr> Map);
    Expr<V> Extent(Expr<S> argA, Expr<T> argB, Expr<U> argC, bool Max);
  }
  public partial interface OperatorX<S, T, U, V, W> : IOperator<W> {
    Expr<W> Derivative(Expr<S> argA, Expr<T> argB, Expr<U> argC, Expr<V> argD, Dictionary<Expr, Expr> Map);
    Expr<W> Extent(Expr<S> argA, Expr<T> argB, Expr<U> argC, Expr<V> argD, bool Max);
  }
  public abstract partial class CastOperator<S, T, CNT> : Operator<S, T, CNT> where CNT : CastOperator<S, T, CNT> {
    public override Expr<T> Extent(Expr<S> argA, bool Max) {
      var a = argA.Extent(Max);
      return Instance.Make(a);
    }
  }


  public partial class BaseOperatorX<S, T> : BaseOperatorX<T>, IOperatorX<S, T> {
    public virtual Expr<T> Derivative(Expr<S> argA, Dictionary<Expr, Expr> Map) {
      var e = Expand(argA);
      if (e != null) return e.Derivative(Map);
      return null;
    }
    public virtual Expr<T> Extent(Expr<S> argA, bool Max) {
      var e = Expand(argA);
      if (e != null) return e.Extent(Max);
      throw new NotSupportedException();
    }
  }
  public partial class BaseOperatorX<S, T, U> {
    public virtual Expr<U> Derivative(Expr<S> argA, Expr<T> argB, Dictionary<Expr, Expr> Map) {
      var e = Expand(argA, argB);
      if (e != null) return e.Derivative(Map);
      return null;
    }
    public virtual Expr<U> Extent(Expr<S> argA, Expr<T> argB, bool Max) {
      var e = Expand(argA, argB);
      if (e != null) return e.Extent(Max);
      throw new NotSupportedException();
    }
  }
  public partial class BaseOperatorX<S, T, U, V> {
    public virtual Expr<V> Derivative(Expr<S> argA, Expr<T> argB, Expr<U> argC, Dictionary<Expr, Expr> Map) {
      var e = Expand(argA, argB, argC);
      if (e != null) return e.Derivative(Map);
      return null;
    }
    public virtual Expr<V> Extent(Expr<S> argA, Expr<T> argB, Expr<U> argC, bool Max) {
      var e = Expand(argA, argB, argC);
      if (e != null) return e.Extent(Max);
      throw new NotSupportedException();
    }
  }
  public partial class BaseOperatorX<S, T, U, V, W> {
    public virtual Expr<W> Derivative(Expr<S> argA, Expr<T> argB, Expr<U> argC, Expr<V> argD, Dictionary<Expr, Expr> Map) {
      var e = Expand(argA, argB, argC, argD);
      if (e != null) return e.Derivative(Map);
      return null;
    }
    public virtual Expr<W> Extent(Expr<S> argA, Expr<T> argB, Expr<U> argC, Expr<V> argD, bool Max) {
      var e = Expand(argA, argB, argC, argD);
      if (e != null) return e.Extent(Max);
      throw new NotSupportedException();
    }
  }
  public partial class Operation<S, T> : BaseOperation<T, IOperatorX<S, T>> {
    protected override Expr<T> Derivative1(Dictionary<Expr, Expr> Map) {
      var ret = Op.Derivative(ArgA, Map);
      return ret;
    }
    public override Expr<T> Extent(bool Max) {
      return Op.Extent(ArgA, Max);
    }
  }
  public partial class Operation<S, T, U> : BaseOperation<U, OperatorX<S, T, U>> {
    protected override Expr<U> Derivative1(Dictionary<Expr, Expr> Map) {
      if (Map.ContainsKey(this)) return (Expr<U>)Map[this];
      var ret = Op.Derivative(ArgA, ArgB, Map);
      if (ret != null) Map[this] = ret;
      return ret;
    }
    public override Expr<U> Extent(bool Max) {
      return Op.Extent(ArgA, ArgB, Max);
    }
  }
  public partial class Operation<S, T, U, V> : BaseOperation<V, OperatorX<S, T, U, V>> {
    protected override Expr<V> Derivative1(Dictionary<Expr, Expr> Map) {
      if (Map.ContainsKey(this)) return (Expr<V>)Map[this];
      var ret = Op.Derivative(ArgA, ArgB, ArgC, Map);
      if (ret != null) Map[this] = ret;
      return ret;
    }
    public override Expr<V> Extent(bool Max) {
      return Op.Extent(ArgA, ArgB, ArgC, Max);
    }
  }
  public partial class Operation<S, T, U, V, W> : BaseOperation<W, OperatorX<S, T, U, V, W>> {
    protected override Expr<W> Derivative1(Dictionary<Expr, Expr> Map) {
      if (Map.ContainsKey(this)) return (Expr<W>)Map[this];
      var ret = Op.Derivative(ArgA, ArgB, ArgC, ArgD, Map);
      if (ret != null) Map[this] = ret;
      return ret;
    }
    public override Expr<W> Extent(bool Max) {
      return Op.Extent(ArgA, ArgB, ArgC, ArgD, Max);
    }
  }
}

namespace Bling.Derivatives {
  public abstract class DeriveInfo : Eval<DeriveEval>.Info {
    public abstract object BaseValue { get; }
  }
  public class DeriveInfo<T> : DeriveInfo, Eval<DeriveEval>.Info<T> {
    public T Value;
    public override object BaseValue { get { return Value; } }
  }
  public class DeriveEval : Eval<DeriveEval> {


    public override Eval<DeriveEval>.Info<T> Constant<T>(Constant<T> constant) {
      return new DeriveInfo<T>() { Value = constant.Value };
    }

    // do not worry about these.
    /*
    public override Eval<DeriveEval>.Info<T> Composite<K, T>(IComposite<K, T> Expr, params Eval<DeriveEval>.Info<K>[] info) {
      throw new NotImplementedException();
    }
    public override Eval<DeriveEval>.Info<K> Access<K, T, ARITY>(Eval<DeriveEval>.Info<T> info, int Idx) {
      throw new NotImplementedException();
    }*/
  }

}